﻿using System;
using System.Reflection;

namespace QuickTest.Core.TestRunner
{
    /// <summary>
    /// The TestLoader class loads and runs tests in a separate app domain.
    /// </summary>
    public class TestLoader : ITestLoader
    {
        private readonly AppDomain domain;
        private readonly ITestPackage testPackage;

        public TestLoader(ITestPackage testPackage)
        {
            if (testPackage == null)
                throw new ArgumentNullException("testPackage");

            this.testPackage = testPackage;

            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
            domain = CreateDomain(testPackage);
        }

        /// <summary>
        /// Starts a test run.
        /// </summary>
        /// <typeparam name="T">Type of test runner to use</typeparam>
        /// <param name="events">Test events to use</param>
        public void Run<T>(ITestEvents events) where T : ITestRunner
        {
            ITestRunner testRunner = CreateTestRunner<T>();

            testRunner.Run(testPackage, events);
        }

        public void Dispose()
        {
            AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
            UnloadDomain();
        }

        private ITestRunner CreateTestRunner<T>() where T : ITestRunner
        {
            return domain.CreateInstanceAndUnwrap(typeof (T).Assembly.FullName, typeof (T).FullName) as ITestRunner;
        }

        private AppDomain CreateDomain(ITestPackage package)
        {
            var info = new AppDomainSetup {ApplicationBase = package.BasePath};

            AppDomain runnerDomain = AppDomain.CreateDomain(String.Format("domain-{0}", package.Name), null, info);

            var assemblyResolver = (AssemblyResolver) runnerDomain.CreateInstanceFromAndUnwrap(
                                                          typeof (AssemblyResolver).Assembly.CodeBase,
                                                          typeof (AssemblyResolver).FullName);

            assemblyResolver.AddFile(typeof (NUnitTestRunner).Assembly.Location);

            return runnerDomain;
        }

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            return (typeof (NUnitTestRunner).Assembly.FullName == args.Name) ? (typeof (NUnitTestRunner).Assembly) : null;
        }

        private void UnloadDomain()
        {
            if (domain != null)
                AppDomain.Unload(domain);
        }
    }
}